FE-1171, FE-1172, FE-1174: fix flaky Playwright tests#8992
FE-1171, FE-1172, FE-1174: fix flaky Playwright tests#8992claude[bot] wants to merge 19 commits into
Conversation
The signin page fetches its Kratos login flow asynchronously after mount, and submitting before the flow has loaded throws without retrying, so the test's waitForURL never resolves. - global-setup: wait for the /self-service/login flow response (registered before page.goto so it can't be missed) before clicking Submit - signin page: disable the Submit button until the flow has loaded, so Playwright's actionability checks also cover this Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VQLxSZtnPp3HfxFycFLwLY
…1174) The tab counts only render once the types contexts finish loading all types (a heavy all-versions query); until then the tabs show just their title with a spinner. The default 5s expect timeout races that query – give each tab-count assertion an explicit 30s timeout instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VQLxSZtnPp3HfxFycFLwLY
getOutgoingLinkAndTargetEntities / getIncomingLinkAndSourceEntities return `undefined` for rightEntity / leftEntity when the link's has-right-entity / has-left-entity edge is not resolved into the subgraph, but LinkEntityAndRightEntity / LinkEntityAndLeftEntity declared those properties as non-optional and an `as` cast hid the mismatch. Consumers indexing `rightEntity[0]` then crash at runtime – most visibly constructOrg, which runs via the auth context on every page and turned a partially-consistent subgraph into an app-wide crash loop. - declare rightEntity / leftEntity as possibly undefined - guard every consumer flagged by the compiler, skipping links whose target/source entity array is missing or empty instead of throwing - add a changeset for @blockprotocol/graph Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VQLxSZtnPp3HfxFycFLwLY
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #8992 +/- ##
==========================================
+ Coverage 59.35% 59.56% +0.20%
==========================================
Files 1373 1387 +14
Lines 134439 136035 +1596
Branches 6066 6185 +119
==========================================
+ Hits 79797 81025 +1228
- Misses 53710 54070 +360
- Partials 932 940 +8
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…tities (FE-1172) Where a link's target is a public entity whose presence in the subgraph is guaranteed by the query (user/org membership targets, parent pages, linear-sync web targets), a missing entity now throws a descriptive error referencing BE-644 instead of being silently skipped. The root cause (subgraph reads not being snapshot-consistent) is being fixed server-side; these errors keep the invariant violation loud if it ever recurs. Genuinely-optional absences (notifications, mentions, links tables, permission-filterable targets) remain graceful.
…ng test timeout (FE-1174) The /types page previously derived its entity type tab counts from the entity types context, which fetches every version of every entity type (including archived ones) with deep constraint resolution - a heavy query that could take longer than the Playwright default expect timeout to resolve. The page now runs its own latestOnly query with constraint resolve depths zeroed (only the inheritance chain is needed to classify link types), and classifies link vs non-link types locally. The 30s expect timeout previously added to the types-page spec is reverted: the default 5s timeout is the page's load budget.
…variables Remove ticket references from invariant-error messages and comments - the errors describe what is wrong, not which ticket tracks it. In constructUser, collect the checked link/target entity revisions instead of re-indexing the revision arrays with non-null assertions afterwards.
Move the linkEntityTypeIds registration after the missing source/target guard in the incoming and outgoing links tables, so a skipped link no longer causes a custom column to render for a link type with no visible rows.
Wherever a filter, find, or guard has already established that a link or target entity revision is present, bind it to a variable and use that instead of re-indexing the array with optional chaining or a non-null assertion: - get-user: collect org memberships and browser-settings targets via flatMap so the checked revisions carry through to their consumers - use-account-pages: carry the checked has-parent link revision through to the invariant error instead of re-indexing - use-flow-runs-usage, service-usage: destructure the single link before the exactly-one guard rather than asserting [0]! after it - save, block-collection-contents: bind the link revision inside the has-data find predicates - org invitations resolver, claims-table: bind the checked link and reuse it in the following clauses
A present link whose target entity is missing from the subgraph must not look like 'no link', or the code creates a duplicate entity and link. - get-user.ts: the me query pairs incoming has-left-entity with outgoing has-right-entity hops and the settings entity is the user's own entity in their own web, so an unresolved target is an invariant violation – throw instead of falling through to create a duplicate settings entity. - block-select-data-modal.tsx: the query entity may live in a different web to the block, so the viewer may see the has-query link without its target. Track the link separately, show an explanatory message instead of the editor in that state, and guard the create path against it.
A has-parent link can be present in the account-pages subgraph while its target has no revisions there: metadata archival closes the entity's decision-time interval so it no longer overlaps the queried interval, and a target hidden from the requester is likewise absent. The stdlib then returns an empty rightEntity array for the resolved edge, so absence is a legal state rather than a malformed subgraph. Restore graceful parentless handling instead of throwing, and document the present-but-empty case on the LinkEntityAndRightEntity/LeftEntity types.
…y has no visible revisions; drop unused parent-link binding
PR Review Summary (re-run)Re-run of the earlier pr-review-toolkit review (which ran at head Status of previous run's findings
Critical Issues (0 found)None. Important Issues (3 found — all NEW)
Suggestions (10 found)
Strengths
Recommended Action
|
…tity caller generics in block collection consumers, and warn when notifications are skipped over unresolved links
|
The three Important findings from the review re-run are addressed in 7240cc3:
|
…, and tolerate the notification-skip warnings in the Playwright console fixture
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 275ecbf. Configure here.
…, and instead fail the save only when a persisted block in the document is missing from the fetched block list

Requested by Tim Diekmann · Slack thread
🌟 What is the purpose of this PR?
Fixes the three flaky Playwright failures tracked in FE-1171, FE-1172, and FE-1174. Each one is a race, and each is fixed at the source rather than by retrying the test.
FE-1171 — global-setup sign-in race
Before: the suite's global setup sometimes timed out on
waitForURL("/")after clicking Submit on/signin. The signin page fetches its Kratos login flow asynchronously after mount; if setup clicked Submit before the flow response arrived,handleSubmitthrew "No sign in flow available" with no retry, so no navigation ever happened and the whole run failed in setup.After: global setup registers a wait for the
/self-service/loginflow response before navigating, and awaits it before clicking Submit. The signin page's Submit button is also disabled until the flow has loaded, so Playwright's own actionability checks (and real users) can no longer submit into the void.FE-1172 — app crash loop from unguarded link-target indexing
Before: specs intermittently found the frontend stuck in an error state on every page.
getOutgoingLinkAndTargetEntities/getIncomingLinkAndSourceEntitiesin@blockprotocol/graphreturnundefinedforrightEntity/leftEntitywhen the link'shas-right-entity/has-left-entityedge isn't resolved into the subgraph, but theLinkEntityAndRightEntity/LinkEntityAndLeftEntitytypes declared those properties non-optional, with anascast hiding the mismatch. Consumers indexingrightEntity[0]then crashed with aTypeError— most visiblyconstructOrg, which runs via the auth context on every page, turning one bad subgraph into an app-wide crash loop. The flakiness trigger is parallel specs acting as the same user: concurrent writes can produce partially-consistent subgraphs in which a link entity is present but its target entity is not.After: the types are honest —
rightEntity/leftEntityare declared possiblyundefined— and every consumer the compiler flagged now handles the absence explicitly, split by whether absence is legal:Invariant violation: …error instead of silently skipping: org membership walking inconstructOrg(user-and-org.ts), parent-page resolution inuse-account-pages.ts, org memberships in the browser plugin'sget-user.ts, andsyncLinearDataWithweb targets inuse-linear-integrations.ts. (The billing path inservice-usage.tsand the block-contents paths insave.ts/block-collection-contents.ts/org/shared.tsalready threw and keep doing so.) The Playwright console fixture fails tests on console errors, so if the server race ever recurs the failure stays loud rather than silently degrading.Behaviour is unchanged when subgraph data is complete. The root cause — subgraph reads not being snapshot-consistent under concurrent writes — is fixed server-side in #8996 (BE-644); this PR deliberately does not silence the error client-side. A changeset is included for
@blockprotocol/graph.FE-1174 — tab-count assertion racing a heavy query
Before: the
/typespage spec asserted each tab shows a non-zero count (e.g.Entity Types12) with the default 5s expect timeout, but the entity type counts only rendered after the entity types context finished loading every version of every entity type (including archived ones) with deep constraint resolution — a heavy query. Under load the tabs still showed just their title with a spinner at the 5s mark, failing the assertion.After: the test's default 5s expect timeout is the page's load budget, so the fix is in the page, not the test. The
/typespage no longer waits on the heavy entity types context for its counts: it runs its ownlatestOnly: truequery with the constraint resolve depths zeroed (only the inheritance chain is needed to classify link types) and classifies link vs non-link types locally. The spec keeps the default expect timeout and the non-zero-count regexes.Remaining latency risk: test artifacts show navigation + app hydration consume roughly 4s of the budget before any query fires, and the heavy all-versions context query is still triggered in the background by the types table (though it no longer blocks the counts). If the budget is still tight in CI, the next lever is app-shell hydration cost, which is out of scope here.
🔗 Related links
🔍 What does this change?
tests/hash-playwright/global-setup.ts(await the Kratos login-flow response before clicking Submit),apps/hash-frontend/src/pages/signin.page.tsx(disable Submit until the flow is loaded)libs/@blockprotocol/graph/src/types/entity.ts(makerightEntity/leftEntityoptional),libs/@blockprotocol/graph/src/stdlib/subgraph/edge/link-entity.ts(document the now-honest return values), plus explicit handling in every consumer the compiler flagged — loud invariant errors where presence is guaranteed (apps/hash-frontend/src/lib/user-and-org.ts,apps/hash-frontend/src/components/hooks/use-account-pages.ts,use-linear-integrations.ts,apps/plugin-browser/src/shared/get-user.ts), graceful skips where absence is legal:notifications-with-links-context.tsx,block-collection-contents.ts,block-select-data-modal.tsx,mention-display.tsx,mention-suggester.tsx,claims-table.tsx, entity-editor links-section (incoming-links-section.tsx,incoming-links-table.tsx,readonly-outgoing-links-table.tsx,use-rows.ts),get-entity-multi-type-dependencies.ts,use-flow-runs-usage.ts,apps/hash-api/src/graphql/resolvers/knowledge/org/shared.ts,apps/plugin-browser/src/shared/get-user.ts,libs/@local/hash-isomorphic-utils/src/save.ts,libs/@local/hash-isomorphic-utils/src/service-usage.ts; changeset in.changeset/fe-1172-optional-link-targets.mdapps/hash-frontend/src/pages/types/[[...type-kind]].page.tsx(page-scopedlatestOnlyentity types query with narrowed resolve depths, replacing the page's dependence on the heavy all-versions entity types context),tests/hash-playwright/tests/features/types-page.spec.ts(default expect timeout kept — the 5s budget is the point of the test)Pre-Merge Checklist 🚀
🚢 Has this modified a publishable library?
This PR:
📜 Does this require a change to the docs?
The changes in this PR:
🕸️ Does this require a change to the Turbo Graph?
The changes in this PR:
LinkEntityAndRightEntity-shaped values with their own explicit generic instantiations (e.g.save.ts,block-collection-contents.ts) still assert non-optional target arrays; the compiler accepts these and their existing runtime guards were left as-is.🐾 Next steps
sleep(500)in thetests/hash-playwright/tests/features/entity-editing.spec.tshelpers is the same class of bug — a fixed delay racing async rendering — and should be replaced with a condition-based wait.🛡 What tests cover this?
global-setup.ts,types-page.spec.ts) exercises FE-1171 and FE-1174 directly; the types-page spec now enforces the 5s load budget with the default expect timeout.tests/hash-backend-integration/src/tests/subgraph/friendship.test.tsalready assertsrightEntity: undefined/rightEntity: []for unresolved and archived targets, which the FE-1172 type change now reflects honestly.lint:tsc) passes for@blockprotocol/graph,@local/hash-isomorphic-utils,@local/hash-backend-utils,@apps/hash-frontend,@apps/hash-api,@apps/plugin-browser,@tests/hash-playwright, and@tests/hash-backend-integration; eslint and oxfmt clean on changed files.❓ How to test this?
Invariant violation …error (the read inconsistency itself is fixed in BE-644: Run subgraph reads in a single READ ONLY, REPEATABLE READ transaction #8996) rather than silently rendering incomplete data; genuinely-optional targets (notifications, links tables, mentions) are still skipped gracefully📹 Demo
N/A — test-stability and defensive-typing changes with no intended UI change (the signin Submit button is now briefly disabled while the login flow loads).
🤖 Generated with Claude Code
https://claude.ai/code/session_01VQLxSZtnPp3HfxFycFLwLY
Generated by Claude Code